﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using LogicalComponent.util.exception;

namespace LogicalComponent.util
{
    /// <summary>
    /// Immutable type for storing a fraction (or rational number).
    /// </summary>
    [Serializable()]
    public class Fraction: IComparable<Fraction>
    {
        private int num;
        private int denom;
        public int Numerator { get { return num; } }
        public int Denominator { get { return denom; } }
        public double DoubleValue { get { return ((double)num) / ((double)denom); } }

        // *********************************************************************
        // **************** CONSTRUCTORS ***************************************
        // *********************************************************************

        /// <summary>
        /// Initializes a new instance of the <see cref="Fraction"/> class.
        /// </summary>
        /// <param name="numerator">The numerator.</param>
        /// <param name="denominator">The denominator.</param>
        public Fraction(int numerator, int denominator) {
            this.num = numerator;
            if (denominator == 0)
            {
                throw new LogicalUserError("Cannot create a Fraction with a denominator of 0");
            }
            this.denom = denominator;
            this.Reduce();
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Fraction"/> class from another Fraction (copy constructor).
        /// </summary>
        /// <param name="other">The Fraction to copy from.</param>
        public Fraction(Fraction other) : this(other.num, other.denom) { }

        /// <summary>
        /// Initializes a new instance of the <see cref="Fraction"/> class from an integer.
        /// </summary>
        /// <param name="intValue">The int value.</param>
        public Fraction(int intValue) : this(intValue, 1) { }

        // *********************************************************************
        // **************** OVERRIDES ******************************************
        // *********************************************************************

        public override string ToString()
        {
            return string.Format("{0}/{1}", this.num, this.denom);
        }

        /// <summary>
        /// Returns a hash code for this instance.
        /// </summary>
        /// <returns>
        /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.
        /// </returns>
        public override int GetHashCode()
        {
            int hash = 1;
            for (int i = 0; i < this.denom; i++)
            {
                hash *= num;
            }
            return hash;
        }

        /// <summary>
        /// Determines whether the specified <see cref="System.Object"/> is equal to this instance.
        /// </summary>
        /// <param name="o">The <see cref="System.Object"/> to compare with this instance.</param>
        /// <returns>
        ///   <c>true</c> if the specified <see cref="System.Object"/> is equal to this instance; otherwise, <c>false</c>.
        /// </returns>
        public override bool Equals(object o)
        {
            if (o == null)
            {
                return false;
            }

            if (o is int)
            {
                return this.num == (int)o && this.denom == 1;
            }

            Fraction f = o as Fraction;
            if ((System.Object)f == null)
            {
                return false;
            }

            return this.num == f.num && this.denom == f.denom;
        }

        /// <summary>
        /// Equalses the specified other.
        /// </summary>
        /// <param name="other">The other.</param>
        /// <returns>Whether or not this is equal to the other fraction.</returns>
        public Boolean Equals(Fraction other)
        {
            if ((object)other == null)
            {
                return false;
            }

            return this.num == other.num && this.denom == other.denom;
        }

        /// <summary>
        /// Equalses the specified other.
        /// </summary>
        /// <param name="other">The other.</param>
        /// <returns>Whether or not this is equal to the integer.</returns>
        public Boolean Equals(int other)
        {
            return this.num == other && this.denom == 1;
        }

        /// <summary>
        /// Compares the current object with another object of the same type.
        /// </summary>
        /// <param name="other">An object to compare with this object.</param>
        /// <returns>
        /// A 32-bit signed integer that indicates the relative order of the objects being compared. The return value has the following meanings:
        /// Value
        /// Meaning
        /// Less than zero
        /// This object is less than the <paramref name="other"/> parameter.
        /// Zero
        /// This object is equal to <paramref name="other"/>.
        /// Greater than zero
        /// This object is greater than <paramref name="other"/>.
        /// </returns>
        public int CompareTo(Fraction other)
        {
            if (this < other) { return -1; }
            // TODO implement this better, right now this hacks sortedList so that
            // it thinks that new fractions are unique keys.
            return 1;
            // else if (this > other) { return 1; }
            // return 0;
        }

        // *********************************************************************
        // **************** OPERATORS ******************************************
        // *********************************************************************

        /// <summary>
        /// Implements the operator ==.
        /// </summary>
        /// <param name="first">The first.</param>
        /// <param name="second">The second.</param>
        /// <returns>
        /// The result of the operator.
        /// </returns>
        public static Boolean operator ==(Fraction first, Fraction second)
        {
            if (System.Object.ReferenceEquals(first, second))
            {
                return true;
            }

            if ((object)first == null || (object)second == null)
            {
                return false;
            }

            return first.num == second.num && first.denom == second.denom;
        }

        /// <summary>
        /// Implements the operator ==.
        /// </summary>
        /// <param name="first">The first.</param>
        /// <param name="second">The second.</param>
        /// <returns>
        /// The result of the operator.
        /// </returns>
        public static Boolean operator ==(Fraction first, int second)
        {
            if ((object)first == null)
            {
                return false;
            }
            return first.Equals(second);
        }

        /// <summary>
        /// Implements the operator ==.
        /// </summary>
        /// <param name="first">The first.</param>
        /// <param name="second">The second.</param>
        /// <returns>
        /// The result of the operator.
        /// </returns>
        public static Boolean operator ==(int first, Fraction second)
        {
            if ((object)second == null)
            {
                return false;
            }
            return second.Equals(first);
        }

        /// <summary>
        /// Implements the operator !=.
        /// </summary>
        /// <param name="first">The first.</param>
        /// <param name="second">The second.</param>
        /// <returns>
        /// The result of the operator.
        /// </returns>
        public static Boolean operator !=(Fraction first, Fraction second)
        {
            return !(first == second);
        }

        /// <summary>
        /// Implements the operator !=.
        /// </summary>
        /// <param name="first">The first.</param>
        /// <param name="second">The second.</param>
        /// <returns>
        /// The result of the operator.
        /// </returns>
        public static Boolean operator !=(Fraction first, int second)
        {
            return !(first == second);
        }

        /// <summary>
        /// Implements the operator !=.
        /// </summary>
        /// <param name="first">The first.</param>
        /// <param name="second">The second.</param>
        /// <returns>
        /// The result of the operator.
        /// </returns>
        public static Boolean operator !=(int first, Fraction second)
        {
            return !(first == second);
        }

        /// <summary>
        /// Implements the operator ++.
        /// </summary>
        /// <param name="f">The f.</param>
        /// <returns>
        /// The result of the operator.
        /// </returns>
        public static Fraction operator ++(Fraction f)
        {
            Fraction f2 = new Fraction(f) ;
            f2 += 1;
            return f2;
        }

        /// <summary>
        /// Implements the operator --.
        /// </summary>
        /// <param name="f">The f.</param>
        /// <returns>
        /// The result of the operator.
        /// </returns>
        public static Fraction operator --(Fraction f)
        {
            Fraction f2 = new Fraction(f);
            f2 -= 1;
            return f2;
        }

        /// <summary>
        /// Implements the operator +.
        /// </summary>
        /// <param name="first">The first.</param>
        /// <param name="second">The second.</param>
        /// <returns>
        /// The result of the operator.
        /// </returns>
        public static Fraction operator +(Fraction first, Fraction second)
        {
            int numerator = first.num * second.denom + second.num * first.denom;
            int denominator = first.denom * second.denom;
            return new Fraction(numerator, denominator);
        }

        /// <summary>
        /// Implements the operator +.
        /// </summary>
        /// <param name="first">The first.</param>
        /// <param name="second">The second.</param>
        /// <returns>
        /// The result of the operator.
        /// </returns>
        public static Fraction operator +(Fraction first, int second)
        {
            return first + new Fraction(second);
        }

        /// <summary>
        /// Implements the operator +.
        /// </summary>
        /// <param name="first">The first.</param>
        /// <param name="second">The second.</param>
        /// <returns>
        /// The result of the operator.
        /// </returns>
        public static Fraction operator +(int first, Fraction second)
        {
            return second + first;
        }

        /// <summary>
        /// Implements the operator -.
        /// </summary>
        /// <param name="first">The first.</param>
        /// <param name="second">The second.</param>
        /// <returns>
        /// The result of the operator.
        /// </returns>
        public static Fraction operator -(Fraction first, Fraction second)
        {
            int numerator = first.num * second.denom - second.num * first.denom;
            int denominator = first.denom * second.denom;
            return new Fraction(numerator, denominator);
        }

        /// <summary>
        /// Implements the operator -.
        /// </summary>
        /// <param name="first">The first.</param>
        /// <param name="second">The second.</param>
        /// <returns>
        /// The result of the operator.
        /// </returns>
        public static Fraction operator -(Fraction first, int second)
        {
            return first - new Fraction(second);
        }

        /// <summary>
        /// Implements the operator -.
        /// </summary>
        /// <param name="first">The first.</param>
        /// <param name="second">The second.</param>
        /// <returns>
        /// The result of the operator.
        /// </returns>
        public static Fraction operator -(int first, Fraction second)
        {
            return new Fraction(first) - second;
        }

        /// <summary>
        /// Implements the operator *.
        /// </summary>
        /// <param name="first">The first.</param>
        /// <param name="second">The second.</param>
        /// <returns>
        /// The result of the operator.
        /// </returns>
        public static Fraction operator *(Fraction first, Fraction second)
        {
            return new Fraction(first.num * second.num, first.denom * second.denom);
        }

        /// <summary>
        /// Implements the operator *.
        /// </summary>
        /// <param name="first">The first.</param>
        /// <param name="second">The second.</param>
        /// <returns>
        /// The result of the operator.
        /// </returns>
        public static Fraction operator *(Fraction first, int second)
        {
            return first * new Fraction(second);
        }

        /// <summary>
        /// Implements the operator *.
        /// </summary>
        /// <param name="first">The first.</param>
        /// <param name="second">The second.</param>
        /// <returns>
        /// The result of the operator.
        /// </returns>
        public static Fraction operator *(int first, Fraction second)
        {
            return second * first;
        }

        /// <summary>
        /// Implements the operator /.
        /// </summary>
        /// <param name="first">The first.</param>
        /// <param name="second">The second.</param>
        /// <returns>
        /// The result of the operator.
        /// </returns>
        public static Fraction operator /(Fraction first, Fraction second)
        {
            return new Fraction(first.num * second.denom, first.denom * second.num);
        }

        /// <summary>
        /// Implements the operator /.
        /// </summary>
        /// <param name="first">The first.</param>
        /// <param name="second">The second.</param>
        /// <returns>
        /// The result of the operator.
        /// </returns>
        public static Fraction operator /(Fraction first, int second)
        {
            return first / new Fraction(second);
        }

        /// <summary>
        /// Implements the operator /.
        /// </summary>
        /// <param name="first">The first.</param>
        /// <param name="second">The second.</param>
        /// <returns>
        /// The result of the operator.
        /// </returns>
        public static Fraction operator /(int first, Fraction second)
        {
            return new Fraction(first) / second;
        }

        /// <summary>
        /// Implements the operator &lt;.
        /// </summary>
        /// <param name="first">The first.</param>
        /// <param name="second">The second.</param>
        /// <returns>
        /// The result of the operator.
        /// </returns>
        public static Boolean operator <(Fraction first, Fraction second)
        {
            return first.num * second.denom < second.num * first.denom;
        }

        /// <summary>
        /// Implements the operator &lt;.
        /// </summary>
        /// <param name="first">The first.</param>
        /// <param name="second">The second.</param>
        /// <returns>
        /// The result of the operator.
        /// </returns>
        public static Boolean operator <(Fraction first, int second)
        {
            return first < new Fraction(second);
        }

        /// <summary>
        /// Implements the operator &lt;.
        /// </summary>
        /// <param name="first">The first.</param>
        /// <param name="second">The second.</param>
        /// <returns>
        /// The result of the operator.
        /// </returns>
        public static Boolean operator <(int first, Fraction second)
        {
            return new Fraction(first) < second;
        }

        /// <summary>
        /// Implements the operator &lt;=.
        /// </summary>
        /// <param name="first">The first.</param>
        /// <param name="second">The second.</param>
        /// <returns>
        /// The result of the operator.
        /// </returns>
        public static Boolean operator <=(Fraction first, Fraction second)
        {
            if (first == second)
            {
                return true;
            }
            return first.num * second.denom < second.num * first.denom;
        }

        /// <summary>
        /// Implements the operator &lt;=.
        /// </summary>
        /// <param name="first">The first.</param>
        /// <param name="second">The second.</param>
        /// <returns>
        /// The result of the operator.
        /// </returns>
        public static Boolean operator <=(Fraction first, int second)
        {
            if (first == second)
            {
                return true;
            }
            return first < new Fraction(second);
        }

        /// <summary>
        /// Implements the operator &lt;=.
        /// </summary>
        /// <param name="first">The first.</param>
        /// <param name="second">The second.</param>
        /// <returns>
        /// The result of the operator.
        /// </returns>
        public static Boolean operator <=(int first, Fraction second)
        {
            if (first == second)
            {
                return true;
            }
            return new Fraction(first) < second;
        }

        /// <summary>
        /// Implements the operator &gt;.
        /// </summary>
        /// <param name="first">The first.</param>
        /// <param name="second">The second.</param>
        /// <returns>
        /// The result of the operator.
        /// </returns>
        public static Boolean operator >(Fraction first, Fraction second)
        {
            return first.num * second.denom > second.num * first.denom;
        }

        /// <summary>
        /// Implements the operator &gt;.
        /// </summary>
        /// <param name="first">The first.</param>
        /// <param name="second">The second.</param>
        /// <returns>
        /// The result of the operator.
        /// </returns>
        public static Boolean operator >(Fraction first, int second)
        {
            return first > new Fraction(second);
        }

        /// <summary>
        /// Implements the operator &gt;.
        /// </summary>
        /// <param name="first">The first.</param>
        /// <param name="second">The second.</param>
        /// <returns>
        /// The result of the operator.
        /// </returns>
        public static Boolean operator >(int first, Fraction second)
        {
            return new Fraction(first) > second;
        }

        /// <summary>
        /// Implements the operator &gt;=.
        /// </summary>
        /// <param name="first">The first.</param>
        /// <param name="second">The second.</param>
        /// <returns>
        /// The result of the operator.
        /// </returns>
        public static Boolean operator >=(Fraction first, Fraction second)
        {
            if (first == second)
            {
                return true;
            }
            return first.num * second.denom > second.num * first.denom;
        }

        /// <summary>
        /// Implements the operator &gt;=.
        /// </summary>
        /// <param name="first">The first.</param>
        /// <param name="second">The second.</param>
        /// <returns>
        /// The result of the operator.
        /// </returns>
        public static Boolean operator >=(Fraction first, int second)
        {
            if (first == second)
            {
                return true;
            }
            return first > new Fraction(second);
        }

        /// <summary>
        /// Implements the operator &gt;=.
        /// </summary>
        /// <param name="first">The first.</param>
        /// <param name="second">The second.</param>
        /// <returns>
        /// The result of the operator.
        /// </returns>
        public static Boolean operator >=(int first, Fraction second)
        {
            if (first == second)
            {
                return true;
            }
            return new Fraction(first) > second;
        }

        // *********************************************************************
        // **************** PRIVATE ********************************************
        // *********************************************************************

        /// <summary>
        /// Reduces the fraction as much as possible.
        /// </summary>
        private void Reduce()
        {
            Boolean numeratorHigher = this.num >= this.denom;
            int higher = numeratorHigher ? this.num : this.denom;
            int lower = numeratorHigher ? this.denom : this.num;

            int gcd = this.GetGcd(higher, lower);
            if (this.num < 0 && this.denom < 0) {
                //gcd *= -1;
            }
            this.num /= gcd;
            this.denom /= gcd;

            if (this.denom < 0)
            {
                this.num *= -1;
                this.denom *= -1;
            }
        }

        /// <summary>
        /// Return The greatest common denominator of 2 numbers using the euclidian algorithm.
        /// </summary>
        /// <param name="higher">The higher.</param>
        /// <param name="lower">The lower.</param>
        /// <returns>The GCD</returns>
        private int GetGcd(int higher, int lower) {
            if (lower == 0)
            {
                return higher;
            }
            int multiplier = higher / lower;
            int newLower = higher - lower * multiplier;
            return GetGcd(lower, newLower);
        }
    }
}
